package com.zhuangjie.security.intercepor;

import com.alibaba.nacos.common.packagescan.resource.AntPathMatcher;
import com.zhuangjie.security.annotations.*;
import com.zhuangjie.security.enums.Logical;
import com.zhuangjie.security.exception.NotFoundTokenException;
import com.zhuangjie.security.exception.TokenInvalidException;
import com.zhuangjie.security.helper.JwtHelper;
import com.zhuangjie.security.helper.UnprocessableResponse;
import com.zhuangjie.security.helper.config.td.AllTokenDataKeys;
import com.zhuangjie.security.utils.ReflectionUtils;
import jodd.util.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;

@Component
@Slf4j
public class UserRequestInterceptor implements HandlerInterceptor {

    @Autowired
    private JwtHelper jwtHelper;

    public static final ThreadLocal<Map<String, Object>> threadLocal = new ThreadLocal();

    /**
     * 挂载登录状态(由未登录状态变为登录状态)
     * 已经确定要挂载的token的颁发模块时使用
     *
     * @param accessToken 访问令牌
     * @throws Exception 例外
     */
    public void tryNotCheckStateMountAnyModuleLoginState(String accessToken, Boolean isParseAuthenticationHeader) throws Exception {
        Map<String, Object> tokenData = jwtHelper.authentication(accessToken, isParseAuthenticationHeader, true, true);
        threadLocal.set(tokenData);
    }

    public Map<String, Object> tryMountAnyModuleLoginState() throws Exception {
        Map<String, Object> tokenData = jwtHelper.authentication(null, true, false, true);
        threadLocal.set(tokenData);
        return tokenData;
    }

    /**
     * 挂载当前模块的登录状态
     * （相当于正常需要权限进入，适用时延迟需要权限认证登录状态。出错会将响应改为401）
     *
     * @return
     * @throws Exception
     */
    public Map<String, Object> tryMountCurrentMountLoginState() throws Exception {
        try {
            Map<String, Object> tokenData = jwtHelper.authentication(null, true, false, false);
            threadLocal.set(tokenData);
            return tokenData;
        }catch (TokenInvalidException e) {
            UnprocessableResponse.respondWith401(null);
            throw e;
        }
    }


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1. 检查是否匹配到处理器
        if (!(handler instanceof HandlerMethod)) {
            // 没有匹配到Controller中的方法
            log.info("安全框架： 调用了不存在的接口`{}`", request.getRequestURL());
            return false;
        }
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        // 2. 检查是否为不需要认证接口（检查是否满足@NoAuthenticationI）
        if (ReflectionUtils.isHasAnnotation(handlerMethod, NoAuthentication.class, true)) {
            return true;
        }
        // 3. 进行认证
        try {
            // 前置：token有效性验证
            Map<String, Object> tokenData = null;
            try {
                tokenData = tryMountAnyModuleLoginState();
            }catch (TokenInvalidException e) {
                UnprocessableResponse.respondWith401("401，请求的接口需要认证，但token无效！");
                return false;
            }catch (NotFoundTokenException e) {
                UnprocessableResponse.respondWith401("401，请求的接口需要认证，但没有找到token！");
                return false;
            }
            // 3.1 @OnlySystemCall 在这里看方法上是否标注为内部接口
            if( ReflectionUtils.isHasAnnotation(handlerMethod, OnlySystemCall.class, true) && !isModuleSecurityAccess(tokenData)) {
                throw new Exception("安全框架：访问该API的不合法（该API只允许内部调用）！");
            }
            // 3.2 检查是否满足@PublicAPI
            PublicAPI publicAPIAnnotation = ReflectionUtils.getAnnotation(handlerMethod, PublicAPI.class, true);
            if (publicAPIAnnotation != null) {
                // 如果加了PublicAPI 且限制了模块，如果请求的模块符不符合会直接决定是否允许访问
                if (!checkIsPublicApiForModulesSatisfied(handlerMethod, tokenData)) {
                    throw new Exception("安全框架：访问该API的不合法（该API不允许该token的模块访问）！");
                }
                return true;
            }

            // 3.3 检查是否满足@RequiresRoles与@RequiresPermissions
            boolean isOtherModuleRequest = isOtherModuleRequest(tokenData);
            RequiresRoles requiresRolesAnnotate = ReflectionUtils.getAnnotation(handlerMethod, RequiresRoles.class, true);
            RequiresPermissions requiresPermissionsAnnotate = ReflectionUtils.getAnnotation(handlerMethod, RequiresPermissions.class, true);
            if (!(requiresRolesAnnotate == null || checkAccess(getTokenModuleName(tokenData), isOtherModuleRequest, requiresRolesAnnotate.modules(), getRoles(tokenData), requiresRolesAnnotate.value(), requiresRolesAnnotate.logical()))
                    || !(requiresPermissionsAnnotate == null || checkAccess(getTokenModuleName(tokenData), isOtherModuleRequest, requiresRolesAnnotate.modules(), getPermissions(tokenData), requiresPermissionsAnnotate.value(), requiresRolesAnnotate.logical()))) {
                throw new Exception("权限检查不通过！");
            }
            if (requiresRolesAnnotate != null || requiresPermissionsAnnotate != null) {
                // 能到这里表示权限校验通过了
                return true;
            }

            // 3.4 如果没有添加以上的任何注解，那只能本模块访问
            if (isOtherModuleRequest) {
                throw new Exception("安全框架：所访问的接口要求的token与请求携带的token颁发的模块不符合！！");
            }
            return true;
        } catch (Exception e) {
            // 不通过，设置401状态码并返回错误消息
            UnprocessableResponse.respondWith403("403,"+e.getMessage());
            return false;
        }
    }

    private boolean isModuleSecurityAccess(Map<String, Object> tokenData) {
        return tokenData != null && Objects.equals(tokenData.get(AllTokenDataKeys.MODULE_SECURITY_ACCESS_KEY),true);
    }

    private boolean checkAccess(String moduleName, boolean isOtherModuleRequest, String[] publicModuleNames, List<String> hasValues, String[] requireValues, Logical logical) {
        // 1. 查看模块是否符合要求
        if (!checkModule(moduleName, isOtherModuleRequest, publicModuleNames)) return false;
        // 2. 查看权限是否符合要求
        // 2.1 基本权限判断
        // 检查是否有@RequiresPermissions，如果有又没有值，则无效， 返回true
        if (requireValues == null || requireValues.length == 0) {
            return true;
        }
        // 到这里表示要求有权限，这里检查是否拥有的权限值为空，为空不满足要求
        if (hasValues == null || hasValues.isEmpty()) {
            return false;
        }
        // 2.2 两者都不为空，进行逻辑权限判断
        // 两个都不为空，且类型都是数组，进行比较,根据permissions annotateRoles logical
        switch (logical) {
            case OR:
                // 只要permissions满足annotateRoles中任意一个可通过
                for (String permission : requireValues) {
                    return hasValues.contains(permission);
                }
                break;
            case AND:
                // 需要permissions包含所有annotateRoles
                for (String permission : requireValues) {
                    if (!hasValues.contains(permission)) {
                        return false;
                    }
                }
                return true;
        }
        return false;
    }

    private String getTokenModuleName(Map<String, Object> tokenData) {
        Object rawRoles = tokenData.get(AllTokenDataKeys.MODULE_NAME_KEY);
        return (rawRoles instanceof String) ? (String) rawRoles : null;
    }

    private boolean checkIsPublicApiForModulesSatisfied(HandlerMethod handlerMethod, Map<String, Object> tokenData) throws Exception {
        if (tokenData == null || tokenData.isEmpty()) return false;
        Object tokenModuleName = tokenData.get(AllTokenDataKeys.MODULE_NAME_KEY);
        if (StringUtil.isBlank(String.valueOf(tokenModuleName))) return false;
        // 获取PublicAPI中的参数
        PublicAPI methodAnnotation = ReflectionUtils.getAnnotation(handlerMethod, PublicAPI.class, true);
        String[] allowModuleNames = methodAnnotation.modules();
        // 匹配器“如使用spark-*”可以用来匹配spark-shop、spark-user等
        AntPathMatcher pathMatcher = new AntPathMatcher();
        for (String allowModuleName : allowModuleNames) {
            if (pathMatcher.match(allowModuleName, (String) tokenModuleName)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        threadLocal.remove();
    }

    private List<String> getPermissions(Map<String, Object> tokenData) {
        Object permissionList = tokenData.get(AllTokenDataKeys.PERMISSIONS_KEY);
        return permissionList instanceof List ? (List<String>) permissionList : null;
    }


    private boolean isOtherModuleRequest(Map<String, Object> tokenData) {
        // 查看token中的模块和当前模块的模块名来判断是否为当前模块的请求
        Object tokenModuleName = tokenData.get(AllTokenDataKeys.MODULE_NAME_KEY);
        String currentModuleName = jwtHelper.jwtHelperConfig.moduleName;
        return !Objects.equals(tokenModuleName, currentModuleName);
    }

    private List<String> getRoles(Map<String, Object> tokenData) {
        Object rawRoles = tokenData.get(AllTokenDataKeys.ROLES_KEY);
        if (rawRoles == null || !(rawRoles instanceof List)) {
            return new ArrayList<>();
        }
        return (List<String>) rawRoles;
    }


    private boolean checkModule(String tokenModuleName, boolean isOtherModuleRequest, String[] modules) {
        // 如果没有指定模块（隐含/默认 是当前模块），又不是当前模块，那就不行
        if (modules.length == 0) return !isOtherModuleRequest;
        // 如果指定了模块，只看模块是否满足
        for (String module : modules) {
            if (module.equals(tokenModuleName)) return true;
        }
        return false;
    }

}

